<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Spring Boot Startup Analysis Report</title>

    <!-- enables tailwindcss @apply -->
    <script src="tailwind.js"></script>
    <!-- https://github.com/jorgebucaran/hyperapp/tree/main/packages -->
    <script src="hyperapp.js"></script>

    <style>
        td {
            padding-left: 8px;
            padding-top: 0;
            padding-bottom: 0;
        }
        summary {
            cursor: pointer;
        }
        summary::-webkit-details-marker {
            display: block;
            margin-left: auto;
            margin-right: auto;
        }
        .VirtualizedTraceView--rowsWrapper, .VirtualizedTraceView--row {
            width: 100%;
        }

        .flex-row {
            display: flex;
            flex: 0 1 auto;
            flex-direction: row;
        }
        .ub-relative {
            position: relative;
        }
        .span-name-column {
            position: relative;
            white-space: nowrap;
            z-index: 1;
        }
        .span-name-wrapper {
            background: #f8f8f8;
            line-height: 27px;
            overflow: hidden;
            display: flex;
        }
        .SpanTreeOffset {
            color: #000;
            position: relative;
        }
        .SpanTreeOffset--indentGuide {
            /* The size of the indentGuide is based off of the iconWrapper */
            padding-right: calc(0.5rem + 12px);
            height: 100%;
            border-left: 3px solid transparent;
            display: inline-flex;
        }

        .SpanTreeOffset--indentGuide:before {
            content: " ";
            padding-left: 1px;
            background-color: #d3d3d3;
        }

        .SpanTreeOffset--iconWrapper {
            position: absolute;
            right: 0.25rem;
        }

        .span-name:after {
            background: transparent;
            bottom: 0;
            content: " ";
            left: 0;
            position: absolute;
            top: 0;
            width: 1000px;
        }
        .span-name {
            color: #000;
            cursor: pointer;
            flex: 1 1 auto;
            outline: none;
            overflow: hidden;
            padding-left: 4px;
            padding-right: 0.25em;
            position: relative;
            text-overflow: ellipsis;
        }
        .span-name:before {
            content: "  ";
            position: absolute;
            top: 4px;
            bottom: 4px;
            left: 0;
            border-left: 4px solid;
            border-left-color: inherit;
        }
        .span-svc-name {
            padding: 0 0.25rem 0 0.5rem;
            font-size: 1.05em;
        }
        .SpanBar--wrapper {
            bottom: 0;
            left: 0;
            position: absolute;
            right: 0;
            top: 0;
            width: 90%;
            overflow: visible;
        }
        .SpanBar--bar {
            border-radius: 3px;
            min-width: 2px;
            position: absolute;
            height: 36%;
            top: 32%;
        }
        .SpanBar--label.is-right {
            left: 100%;
        }
        .SpanBar--label {
            color: #aaa;
            font-size: 12px;
            font-family: Helvetica Neue,Helvetica,Arial,sans-serif;
            line-height: 1em;
            white-space: nowrap;
            padding: 0 0.5em;
            position: absolute;
        }
        .divider-horizontal {
            display: block;
            clear: both;
            height: 1px;
            margin-top: 0.25rem;
            margin-bottom: 0.25rem;
            background: #ddd;
        }
        .divider-vertical {
            position: relative;
            top: -0.06em;
            display: inline-block;
            width: 1px;
            height: 0.9em;
            margin: 0 8px;
            vertical-align: middle;
            background: #ddd;
        }

        .github-corner:hover .octo-arm {
            animation:octocat-wave 560ms ease-in-out
        }
        @keyframes octocat-wave {
            0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}
        }
        @media (max-width:500px) {
            .github-corner:hover .octo-arm {
                animation:none
            }
            .github-corner .octo-arm {
                animation:octocat-wave 560ms ease-in-out
            }
        }

    </style>

    <style type="text/tailwindcss">
        .col-icon {
            @apply w-auto text-center border px-4 py-2;
        }


        .col-name {
            @apply w-2/6 border px-4 py-2;
        }

        .col-jar-name {
            @apply w-5/6 border px-4 py-2;
        }

        .col-invoke-method {
            @apply w-3/6 border px-4 py-2;
        }

        .col-invoke-other {
            @apply w-1/6 border px-4 py-2;
        }

        .col-duration {
            @apply w-1/6 border px-4 py-2 text-center;

        }

        .col-details {
            @apply w-2/6 border px-4 py-2 text-left;
        }

        .event-table {
            @apply rounded w-full table-auto break-all bg-gray-100 text-gray-700;
        }

        .top-header {
            @apply text-white;
            background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' version='1.1' xmlns:xlink='http://www.w3.org/1999/xlink' xmlns:svgjs='http://svgjs.com/svgjs' width='2880' height='560' preserveAspectRatio='none' viewBox='0 0 2880 560'%3e%3cg mask='url(%26quot%3b%23SvgjsMask1232%26quot%3b)' fill='none'%3e%3crect width='2880' height='560' x='0' y='0' fill='%230e2a47'%3e%3c/rect%3e%3cpath d='M2880 0L2844.44 0L2880 242.95z' fill='rgba(255%2c 255%2c 255%2c .1)'%3e%3c/path%3e%3cpath d='M2844.44 0L2880 242.95L2880 245.85L2824.37 0z' fill='rgba(255%2c 255%2c 255%2c .075)'%3e%3c/path%3e%3cpath d='M2824.37 0L2880 245.85L2880 425.25L1507.77 0z' fill='rgba(255%2c 255%2c 255%2c .05)'%3e%3c/path%3e%3cpath d='M1507.77 0L2880 425.25L2880 428.06L782.93 0z' fill='rgba(255%2c 255%2c 255%2c .025)'%3e%3c/path%3e%3cpath d='M0 560L267.89 560L0 488.67z' fill='rgba(0%2c 0%2c 0%2c .1)'%3e%3c/path%3e%3cpath d='M0 488.67L267.89 560L1471.29 560L0 220.12z' fill='rgba(0%2c 0%2c 0%2c .075)'%3e%3c/path%3e%3cpath d='M0 220.12L1471.29 560L1660.45 560L0 90.49000000000001z' fill='rgba(0%2c 0%2c 0%2c .05)'%3e%3c/path%3e%3cpath d='M0 90.49000000000001L1660.45 560L2235.46 560L0 38.31000000000001z' fill='rgba(0%2c 0%2c 0%2c .025)'%3e%3c/path%3e%3c/g%3e%3cdefs%3e%3cmask id='SvgjsMask1232'%3e%3crect width='2880' height='560' fill='white'%3e%3c/rect%3e%3c/mask%3e%3c/defs%3e%3c/svg%3e");
        }
    </style>
</head>
<body class="bg-gray-400">

<div id="startup-analyzer-header"></div>

<div id="spring-startup-statistics"></div>

<!-- spring bean initialization table -->
<div id="spring-bean-initialization"></div>

<!-- bean initialization timeline -->
<div id="bean-initialization-timeline"></div>

<!-- method invoke detail table -->
<div id="method-invoke-detail"></div>

<!-- unused jars after startup -->
<div id="unused-jars-after-startup"></div>

<!-- spring startup frame graph -->
<div id="spring-startup-frame-graph"></div>


<!-- top-right corner GitHub link -->
<a href="https://github.com/linyimin0812/spring-startup-analyzer" target="_blank" class="github-corner" aria-label="View source on GitHub">
    <svg width="65" height="65" viewBox="0 0 250 250" style="fill:#FD6C6C; color:#fff; position: absolute; top: 0; border: 0; right: 0;" aria-hidden="true">
        <path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path>
        <path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm">
        </path>
        <path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body">
        </path>
    </svg>
</a>

<script type="module">
    //<![CDATA[
    // data from startup profiler
    const startupVO = /*startupVO:*/{};
    const flameGraphUrl = /*flameGraphUrl*/'';

    let beanInitResultList = JSON.parse(startupVO.beanInitResultList ? startupVO.beanInitResultList : '[]');
    beanInitResultList = beanInitResultList.filter(beanInitResult => beanInitResult.parentId === 0);
    beanInitResultList.sort((o1, o2) => o2.duration - o1.duration);

    const beanInitResultTimeLineList = [...beanInitResultList].sort((o1, o2) => o1.startMillis - o2.startMillis);
    const maxEndMillis = beanInitResultTimeLineList.reduce((prev, current) => prev.endMillis > current.endMillis ? prev : current).endMillis;
    const minStartMillis = beanInitResultTimeLineList.reduce((prev, current) => prev.startMillis < current.startMillis ? prev : current).startMillis;
    const totalDuration = beanInitResultList.reduce((sum, current) => sum + current.duration, 0);

    let statisticsList = JSON.parse(startupVO.statisticsList ? startupVO.statisticsList : '[]');
    statisticsList.sort((o1, o2) => o1.order - o2.order);

    const unusedJarMap = JSON.parse(startupVO.unusedJarMap ? startupVO.unusedJarMap : '{}');
    const unusedJarEntries = Object.entries(unusedJarMap).sort((o1, o2) => o2[1].length - o1[1].length);

    const methodInvokeDetailList = JSON.parse(startupVO.methodInvokeDetailList ? startupVO.methodInvokeDetailList : '[]');

    // table
    const colorNames = ['yellow', 'green'];
    const levels = [100, 200, 300, 400, 500];
    const colors = ['gray', ...colorNames.flatMap(color => levels.map(l => `${color}-${l}`))];

    const Details = (tags) =>
        h("ul", {}, Object.entries(tags).map(([k, v]) => h("li", {}, [
            h("strong", {}, text(k + ": ")),
            text(v)
        ])));

    const zoomOut = () =>
        h("div", {
            innerHTML: `
            <svg class="w-6 h-6"
             fill="none"
             stroke="currentColor"
             viewBox="0 0 24 24"
             xmlns="http://www.w3.org/2000/svg">
            <path stroke-linecap="round"
                  stroke-linejoin="round"
                  stroke-width="2"
                  d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0zM13 10H7"></path>
        </svg>`
        }, [])

    const zoomIn = () =>
        h("div", {
            innerHTML: `
            <svg xmlns="http://www.w3.org/2000/svg"
             fill="none"
             viewBox="0 0 24 24"
             stroke-width="1.5"
             stroke="currentColor"
             class="w-6 h-6">
            <path stroke-linecap="round"
                  stroke-linejoin="round"
                  d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607zM10.5 7.5v6m3-3h-6"/>
        </svg>`
        }, []);

    const Placeholder = () =>
        h("div", {
            innerHTML: `&nbsp;`,
            style: {width: '24px'}
        }, [])

    const BeanInitResultRowIcon = (event, expandedEventIds, minDuration, search) => {

        const children = event.children.filter(child => FilterEvent(child, minDuration, search));

        if (children.length > 0) {
            if (expandedEventIds.indexOf(event.id) === -1) {
                return zoomIn();
            }

            if (expandedEventIds.indexOf(event.id) !== -1) {
                return zoomOut();
            }
        }

        return Placeholder();
    }

    const BeanInitResultRow = (event, expandedEventIds, minDuration, search, level = 0) => h("tbody", {}, [
        h("tr", {class: `cursor-pointer hover:bg-blue-200 bg-${colors[level]}`, onclick: [Expand, event]}, [
            h("td", {class: "col-icon"}, BeanInitResultRowIcon(event, expandedEventIds, minDuration, search)),
            h("td", {class: `col-name`}, text(event.name)),
            h("td", {class: "col-duration"}, text(event.duration)),
            h("td", {class: "col-duration"}, text(event.actualDuration)),
            h("td", {class: "col-details text-sm"}, Details(event.tags))]
        ),
        h("tr", {}, [
            h("td", {colspan: 5, class: {hidden: expandedEventIds.indexOf(event.id) === -1}},
                event.children
                    .filter(event => FilterEvent(event, minDuration, search))
                    .map(event => BeanInitResultRow(event, expandedEventIds, minDuration, search, level + 1))
            )
        ])
    ]);

    const spanTreeOffset = (level) => {
        const spanTreeOffsets = [h("span", { class: "SpanTreeOffset--indentGuide" })];
        for (let i = 0; i < level; i++) {
            spanTreeOffsets.push(h("span", { class: "SpanTreeOffset--indentGuide" }));
        }

        return spanTreeOffsets;
    };

    const timeLineZoomIn = (event) => h("span", { class: "SpanTreeOffset--iconWrapper" }, [
        h("svg", { class: "fill-current text-current inline-block align-middle w-4 h-4 cursor-pointer", viewBox: "0 0 40 40", onclick: [Expand, event]}, [
            h("g", {}, [
                h("path", { d: "m23.3 20l-13.1-13.6c-0.3-0.3-0.3-0.9 0-1.2l2.4-2.4c0.3-0.3 0.9-0.4 1.2-0.1l16 16.7c0.1 0.1 0.2 0.4 0.2 0.6s-0.1 0.5-0.2 0.6l-16 16.7c-0.3 0.3-0.9 0.3-1.2 0l-2.4-2.5c-0.3-0.3-0.3-0.9 0-1.2z" }),
            ]),
        ]),
    ]);

    const timeLineZoomOut = (event) => h("span", { class: "SpanTreeOffset--iconWrapper" }, [
        h("svg", { class: "fill-current text-current inline-block align-middle w-4 h-4 cursor-pointer", viewBox: "0 0 40 40", onclick: [Expand, event]}, [
            h("g", {}, [
                h("path", { d: "m31 12.5l1.5 1.6-12.5 13.4-12.5-13.4 1.5-1.6 11 11.7z" }),
            ]),
        ]),
    ]);

    const spanTreeOffsetIcon = (event, expandedEventIds, minDuration, search) => {

        const children = event.children.filter(child => FilterEvent(child, minDuration, search));

        if (children.length > 0) {

            if (expandedEventIds.indexOf(event.id) === -1) {
                return [timeLineZoomIn(event)];
            }
            return [timeLineZoomOut(event)];
        }

        return [];
    }

    const calculateLeftPercent = (beanInitResult) => {
        return (beanInitResult.startMillis - minStartMillis) / (maxEndMillis - minStartMillis) * 100 + '%';
    };

    const calculateWidthPercent = (beanInitResult) => {
        return beanInitResult.duration / totalDuration * 100 + '%';
    };

    const ExpandDetails = (state, event) => {
        if (state.expandDetailIds.indexOf(event.id) === -1) {
            return {...state, expandDetailIds: [event.id, ...state.expandDetailIds]};
        } else {
            return {...state, expandDetailIds: state.expandDetailIds.filter(it => it !== event.id)};
        }
    };

    const BeanInitResultTagRow = (key, value) => h("tbody", {}, [
        h("tr", {class: 'cursor-pointer hover:bg-blue-200'}, [
            h("td", {class: "col-invoke-other"}, text(key)),
            h("td", {class: "col-name"}, text(value)),
        ])
    ]);

    const BeanInitResultTimeLineRow = (event, expandedEventIds, expandDetailIds, minDuration, search, level) => h("div", { class: `VirtualizedTraceView--row ${level < 0 ? 'hidden' : ''} ` }, [
        h("div", { class: "flex-row span-row hover:bg-blue-200" }, [
            h("div", { class: "ub-relative span-name-column w-2/5 max-w-2/5" }, [
                h("div", { class: "span-name-wrapper flex hover:bg-blue-200" }, [
                    h("span", { class: "SpanTreeOffset is-parent"}, [
                        ...spanTreeOffset(level),
                        ...spanTreeOffsetIcon(event, expandedEventIds, minDuration, search)
                    ]),
                    h("a", { class: "span-name bl border-teal-500", onclick: [ExpandDetails, event] }, [
                        h("span", { class: "span-svc-name is-children-collapsed" }, text(event.name)),
                    ]),
                ]),
            ]),
            h("div", { class: "ub-relative span-view cursor-pointer flex-grow-0 flex-shrink-0 max-w-3/5 w-3/5", onclick: [ExpandDetails, event] }, [
                h("div", { class: "SpanBar--wrapper" }, [
                    h("div", { class: 'SpanBar--bar bg-teal-500', style: {left: `${calculateLeftPercent(event)}`, width: `${calculateWidthPercent(event)}`}}, [
                        h("div", { class: "SpanBar--label is-right" }, text(event.duration + 'ms')),
                    ]),
                ]),
            ]),
        ]),
        h("div", {class: {hidden: expandDetailIds.indexOf(event.id) === -1}}, [
            h("div", {style: {paddingLeft: `${(level + 1) * 24}px`}}, [
                h("span", {class: `divider-horizontal`}, []),
                h("span", {class: 'text-gray-600 mr-2 text-sm'}, text("Bean Name:")),
                h("strong", {class: 'text-sm'}, text(event.name)),
                h("span", {class: 'divider-vertical'}, []),
                h("span", {class: 'text-gray-600 mr-2 text-sm'}, text("Start Time:")),
                h("strong", {class: 'text-sm'}, text(new Date(event.startMillis).toLocaleString())),
                h("span", {class: 'divider-vertical'}, []),
                h("span", {class: 'text-gray-600 mr-2 text-sm'}, text("Duration:")),
                h("strong", {class: 'text-sm'}, text(event.actualDuration + 'ms')),
                h("span", {class: 'divider-vertical'}, []),
                h("span", {class: 'text-gray-600 mr-2 text-sm'}, text("Duration with Children:")),
                h("strong", {class: 'text-sm'}, text(event.duration + 'ms')),
                h("div", {}, [
                    h("table", {class: "event-table"}, [
                        h("thead", {}, [
                            h("tr", {class: "font-medium"}, [
                                h("td", {class: "col-invoke-other"}, text("Key")),
                                h("td", {class: "col-name"}, text("Value")),
                            ])
                        ]),
                        ...Object.entries(event.tags ? event.tags : {}).map(([key, value]) => BeanInitResultTagRow(key, value))
                    ])
                ]),
                h("span", {class: `divider-horizontal`}, []),
            ])
        ]),
        h("div", {colspan: 5, class: {hidden: expandedEventIds.indexOf(event.id) === -1}},
            event.children
                .filter(event => FilterEvent(event, minDuration, search))
                .map(event => BeanInitResultTimeLineRow(event, expandedEventIds, expandDetailIds, minDuration, search, level + 1))
        )
    ]);

    const UnusedJarMapRow = (entry) => h("tbody", {}, [
        h("tr", {class: 'cursor-pointer hover:bg-blue-200 bg-grey-100'}, [
            h("td", {class: 'col-jar-name'}, [
                h("details", {}, [
                    h("summary", {class: "p-2"}, [
                        h('span', {class: 'font-medium mr-2 ml-4'}, text(entry[0]))
                    ]),
                    h('ul', {class: "p-2"}, [
                        ...entry[1].map((jar, index) => h('li', {}, text(`${index + 1}. ${jar}`)))
                    ])
                ])
            ]),
            h("td", {class: "col-duration"}, text(entry[1].length)),
        ]),
    ]);

    const MethodInvokeDetailRow = (invokeDetail) => h("tbody", {}, [
        h("tr", {class: 'cursor-pointer hover:bg-blue-200 bg-grey-100'}, [
            h("td", {class: 'col-invoke-method'}, [
                h("details", {}, [
                    h("summary", {class: "p-2"}, [
                        h('span', {class: 'font-medium mr-2 ml-4'}, text(invokeDetail.method))
                    ]),
                    h("table", {class: "event-table"}, [
                        h("thead", {}, [
                            h("tr", {class: "font-medium"}, [
                                h("td", {class: "col-invoke-method text-center"}, text("Argument")),
                                h("td", {class: "col-invoke-other text-center"}, text("Cost(ms)")),
                            ]),
                            ...invokeDetail.invokeDetails.map(detail => h("tr", {class: 'cursor-pointer hover:bg-blue-200 bg-grey-100'}, [
                                h("td", {class: "col-invoke-method"}, [
                                    h('ul', {class: "p-2"}, [
                                        ...detail.args.map((arg, index) => h('li', {}, text(`${index + 1}. ${arg}`)))
                                    ])
                                ]),
                                h("td", {class: "col-invoke-other text-center"}, text(detail.duration)),
                            ]))
                        ])
                    ])
                ])
            ]),
            h("td", {class: "col-invoke-other text-center"}, text(invokeDetail.invokeCount)),
            h("td", {class: "col-invoke-other text-center"}, text(invokeDetail.totalCost)),
            h("td", {class: "col-invoke-other text-center"}, text(invokeDetail.averageCost)),
        ]),
    ]);

    const Expand = (state, event) => {
        if (event.children.length > 0 && state.expandedEventIds.indexOf(event.id) === -1) {
            return {...state, expandedEventIds: [event.id, ...state.expandedEventIds]};
        } else {
            return {...state, expandedEventIds: state.expandedEventIds.filter(it => it !== event.id)};
        }
    };

    const OnDurationChange = (state, target) => {
        return {...state, duration: target.value}
    }

    const OnSearchChange = (state, target) => {
        return {...state, search: target.value}
    }

    function hasMinimumDuration(event, minimumDuration) {
        return event.duration >= minimumDuration;
    }

    function labelMatches(event, search) {
        return search === '' || event.name.toLowerCase().includes(search.toLowerCase()) || Object.entries(event.tags).map(([k, v]) => k+v).filter(it => it.toLowerCase().includes(search.toLowerCase())).length > 0;
    }

    function childrenMatch(event, minimumDuration, search) {

        if (search === '' || event.children.filter(it => hasMinimumDuration(it, minimumDuration) && labelMatches(it, search)).length > 0) {
            return true;
        }

        if (!event.children || event.children.length === 0) {
            return false;
        }

        for (const child of event.children) {
            if (childrenMatch(child, minimumDuration, search)) {
                return true;
            }
        }
    }

    const FilterEvent = (event, minimumDuration, search) => {
        return hasMinimumDuration(event, minimumDuration) && (labelMatches(event, search) || childrenMatch(event, minimumDuration, search));
    }

    const FilterView = (state) => h("div", {class: "mb-4"}, [
        h("span", {
            class: 'font-medium mr-2 '
        }, [ text("Minimum duration(ms)")]),
        h("input", {
            type: "text",
            class: 'px-4 w-32 text-left text-gray-800',
            value: state.duration,
            oninput: (_, { target }) => [OnDurationChange, target]}, []),
        h("span", {
            class: 'font-medium mr-2 ml-4'
        }, [ text("Search")]),
        h("input", {
            type: "text",
            class: 'px-4 w-64 text-left text-gray-800',
            value: state.search,
            oninput: (_, { target }) => [OnSearchChange, target]}, [])
    ]);

    // header
    app({
        view: () => h("div", {}, [
            h("div", {class: "p-4 w-full bg-gray-100 text-gray-700 top-header"}, [
                h("h2", {class: "text-3xl"}, text("🍃 Spring Startup Analyzer"))
            ]),
        ]),
        node: document.getElementById("startup-analyzer-header"),
    });

    // startup-statistics
    app({
        view: () => h("div", {}, [
            h("div", {class: "pl-4 pr-4 pt-2 pb-2 w-full bg-gray-300"}, [
                h("h2", {class: "font-medium ml-2"}, text("Startup Statistics")),
            ]),
            h("div", {class: "p-4 w-full bg-gray-400 flex items-center w-full"}, [
                ...statisticsList.map(statistics => h("div", {class: "flex flex-col items-center border bg-gray-200 border-gray-100 rounded-md ml-8 p-2"}, [
                    h("div", {class: "text-xs text-gray-0"}, text(statistics.label)),
                    h("div", {class: "text-base font-bold text-gray-800"}, text(statistics.value)),
                ]))
            ]),
        ]),
        node: document.getElementById("spring-startup-statistics"),
    });

    // spring bean initialization
    app({
        init: {events: beanInitResultList, expandedEventIds: [], duration: 100, search: ''},
        view: (state) => h("div", {class: 'border-t border-gray-100'}, [
            h("details", {open: true}, [
                h("summary", {class: "p-2 w-full bg-gray-300"}, [
                    h("span", {class: "font-medium ml-2"}, text("Spring Bean Initialization")),
                ]),
                h("div", {class: "p-4 w-full bg-gray-400"}, [
                    FilterView(state),
                    h("table", {class: "event-table"}, [
                        h("thead", {}, [
                            h("tr", {class: "font-medium"}, [
                                h("td", {class: "col-icon"}, text("")),
                                h("td", {class: "col-name"}, text("Name")),
                                h("td", {class: "col-duration"}, text("Duration with children (ms)")),
                                h("td", {class: "col-duration"}, text("Duration (ms)")),
                                h("td", {class: "col-details"}, text("Details"))
                            ])
                        ]),
                        ...state.events
                            .filter(event => FilterEvent(event, state.duration, state.search))
                            .map(event => BeanInitResultRow(event, state.expandedEventIds, state.duration, state.search))
                    ])
                ])
            ]),
        ]),
        node: document.getElementById("spring-bean-initialization"),
    });

    // bean initialization timeline
    app({
        init: {events: beanInitResultTimeLineList, expandedEventIds: [], expandDetailIds: [], duration: 100, search: ''},
        view: (state) => h("div", {class: 'border-t border-gray-100'}, [
            h("details", {open: true}, [
                h("summary", {class: "p-2 w-full bg-gray-300"}, [
                    h("span", {class: "font-medium ml-2"}, text("Bean Initailization Timeline")),
                ]),
                h("div", {class: "p-4 w-full bg-gray-400"}, [
                    FilterView(state),
                    h("div", {class: "p-1 w-full bg-white"}, [
                        ...state.events
                            .filter(event => FilterEvent(event, state.duration, state.search))
                            .map(event => BeanInitResultTimeLineRow(event, state.expandedEventIds, state.expandDetailIds, state.duration, state.search, 0))
                    ]),
                ])
            ]),
        ]),
        node: document.getElementById("bean-initialization-timeline"),
    });

    // method invoke detail
    app({
        view: () => h("div", {class: 'border-t border-gray-100'}, [
            h("details", {open: true}, [
                h("summary", {class: "p-2 w-full bg-gray-300"}, [
                    h("span", {class: "font-medium ml-2"}, text("Details of Method Invoke")),
                ]),
                h("div", {class: "p-4 w-full bg-gray-400"}, [
                    h("table", {class: "event-table"}, [
                        h("thead", {}, [
                            h("tr", {class: "font-medium"}, [
                                h("td", {class: "col-invoke-method text-center"}, text("Method")),
                                h("td", {class: "col-invoke-other text-center"}, text("Invoke Count")),
                                h("td", {class: "col-invoke-other text-center"}, text("Total Cost(ms)")),
                                h("td", {class: "col-invoke-other text-center"}, text("Average Cost(ms)")),
                            ])
                        ]),
                        ...methodInvokeDetailList.map(invokeDetail => MethodInvokeDetailRow(invokeDetail))
                    ])
                ]),
            ]),
        ]),
        node: document.getElementById("method-invoke-detail"),
    });

    // unused jars after startup
    app({
        view: () => h("div", {class: 'border-t border-gray-100'}, [
            h("details", {open: true}, [
                h("summary", {class: "p-2 w-full bg-gray-300"}, [
                    h("span", {class: "font-medium ml-2"}, text("Unused Jars After Startup")),
                ]),
                h("div", {class: "p-4 w-full bg-gray-400"}, [
                    h("table", {class: "event-table"}, [
                        h("thead", {}, [
                            h("tr", {class: "font-medium"}, [
                                h("td", {class: "col-jar-name text-center"}, text("ClassLoader")),
                                h("td", {class: "col-duration"}, text("Unused Jar Count")),
                            ])
                        ]),
                        ...unusedJarEntries.map(entry => UnusedJarMapRow(entry))
                    ])
                ]),
            ]),
        ]),
        node: document.getElementById("unused-jars-after-startup"),
    });

    // Startup Wall Clock Profile
    if (flameGraphUrl !== '') {
        app({
            view: () => h("div", {class: 'border-t border-gray-100'}, [
                h("details", {open: true}, [
                    h("summary", {class: "p-2 w-full bg-gray-300"}, [
                        h("span", {class: "font-medium ml-2"}, text("Startup Wall Clock Profile")),
                    ]),
                    h("div", {class: "w-full p-4 bg-gray-400", style: { height: "1000px"},}, [
                        h("iframe", {
                            class: "w-full h-full",
                            src: flameGraphUrl,
                        }, []),
                    ]),
                ]),
            ]),
            node: document.getElementById("spring-startup-frame-graph"),
        });
    }
    //]]>
</script>
</body>
</html>